#include <iostream>
#include <stdio.h>
#include <string>
#include <map>
#include<sys/stat.h> 
#include <iconv.h>
#include <math.h>
#include "Ntfs.h"
#include "../nfs.h"
#include "template/NtfsTemplate.cpp"
#include <string.h>

class Ntfs : public Nfs {
public:
    NTFS_BOOT_SECTOR boot_sector;
    long size, itemSize, giveupNum; // 磁盘总扇区，磁盘簇大小，放弃节点数量 
    int config_jumpM,config_minBlockSize,config_maxFileSize; //寻找文件节点跳跃间距 16M,最小的mft块间距, 最大文件扇区数

    long fileIndex;
    long postion;
    bool nextCheck;

    MFT_RECORD *mft_record;
    FILE_NAME_ATTR *name;
    map <int, ATTR_RECORD *> attrs;
    MFT_BLOCK block;
    vector <MFT_BLOCK> mft_blocks, mft_run;

    Ntfs() {
        giveupNum = 0;
        config_jumpM = 64;
        config_minBlockSize=1024;
        config_maxFileSize=4*1024*1024;   //2G
        nextCheck=false;
        this->init();
        template_init();
    }

    bool setJumpM(int j) {      this->config_jumpM = j;   }

    DiskInfo getDiskInfo() {
        DiskInfo dinfo = {size, itemSize};
        return dinfo;
    }

    long next() { 
        postion+=2;
        if (!this->toPostion()) {
            this->findNext();
            if (postion > boot_sector.number_of_sectors) {
                printf("\n-----------------------\n");               
                this->dumpBlocks(&mft_blocks);
                return -1;
            }
        }else   if(this->nextCheck) {
            this->checkPostion();
        }
//        fileIndex = (postion - block.postion)/2 + block.index;
        fileIndex = (long)mft_record->mft_record_number;
        return fileIndex;
    }

    string getData(FileInode file, long start = 0, long len = 0) {
        postion = file.postion;
        this->toPostion();
        return this->readFile(start, len);
    }

    void dump(long pos, long size = 0xFF) {
        postion = pos;fileIndex=0;
        this->toPostion();
       this->_dump(size);
    }

    FileInode getInode(long pos = 0) {
        if (pos) {
            postion = pos;
            if(!this->toPostion()) throw new Exception("定位文件节点失败");
        }
        return this->getInfo();
    }

private:

    void init() {
      
        char *data = ds->get(0);
        boot_sector = *((NTFS_BOOT_SECTOR *) data);
        this->size = (long long) boot_sector.number_of_sectors;
        this->itemSize = boot_sector.sectors_per_cluster;
        ds->size = this->size;
        
        if (strncmp(boot_sector.oem_id, "NTFS", 4) != 0) throw new Exception("错误：磁盘不是ntfs格式");
        long mft = boot_sector.mft_lcn * boot_sector.sectors_per_cluster;
        MFT_BLOCK b0 = {mft, 0,0};
        mft_blocks.push_back(b0);
        block = mft_blocks.front();

        postion = mft-2;
        //组织 mft数据块信息
        this->next();

        FILE_DATA_ATTR *mft_data = (FILE_DATA_ATTR *) this->attrs[0x80];
        char *run = mft_data->run;
        long index = 0;
        MFT_BLOCK a = {0, 0};

        //获取默认mft文件块信息  
        while (true) {
            long lcn, size;
            int len = this->parseRunData(run, lcn, size);

            MFT_BLOCK b;
            b.postion = lcn + a.postion;
            b.index = index;
            b.size = size;
            index += size / 2;
            mft_run.push_back(b);

            a = b;
            //            printf("--- %d,%d,%d,%2X\n",size,n1,n2,run[n1+n2+1]);
            if (run[len] == 0) break;
            else run += len;
        }
        this->dumpBlocks(&mft_run);
        
        postion = mft-2;        
        //        printf("%d,%.0f\n",boot_sector.bytes_per_sector,(double)boot_sector.number_of_sectors); 
        //        printf("%p,%p\n",boot_sector,&boot_sector.mft_lcn);
        //        ds->dump((char*)&boot_sector.mft_lcn,0x40);  
    }

    /**
     * 给出文件存储结构信息
     * @return  FileInode
     */
    FileInode getInfo() {
        FileInode file;
        char *filename = this->parseFileName();
        //            strcpy(file.name,filename);  free(filename);
        file.name = filename;
        file.parent = Nfs::strLenVal((char *) &name->parent_directory, 6);
        file.type = (int) mft_record->flags;
        file.size = name->data_size;
        file.postion = this->postion;        file.ctime = name->creation_time;
        
        //文件大小为0 并且是文件类型 尝试通过其他方式获取文件大小信息 
        if (file.type < 2) {
            ATTR_RECORD *attr = this->attrs[0x80];
            //                 ds->dump((char *)attr,0x90);
            //                 printf("%p,%p",mft_record,attr);exit(0);
            if (attr) {
                file.size = (attr->non_resident == 0) ? attr->data.resident.value_length : attr->data.non_resident.data_size;
            } else if (!file.size) {
                file.size = name->allocated_size;
            }
        }

        return file;
    }
    
    /**
     * 获取所有属性 
     */
    map <int, ATTR_RECORD *> fileAttrs() {
        char *attr = (char *) mft_record;
        attr += (int) mft_record->attrs_offset;
        map <int, ATTR_RECORD *> rt;

        do {
            ATTR_RECORD *ar = (ATTR_RECORD *) attr;
            //处理重复属性 比如030属性有多个 那就30,31,32 最多10个 
            if(rt.count(ar->type)==0)
                rt[ar->type] = ar;
            else
            {
                for(int i=1;i<10;i++) {
                    if(rt.count(ar->type+i)==0){
                        rt[ar->type+i] = ar;
                        break;
                    }
                }
            }
            if (!ar->length || ar->type == 0xffffffff) break;
            attr += (short) ar->length;
        } while (attr - (char *) mft_record < 1024);

        return rt;
    }
    
    /**
     * 读取文件内容
     * @param readStart 跳过长度  
     * @param readLen   读取长度(单位字节） 
     * @return 
     */
    string readFile(long readStart = 0, long readLen = 0) {
        string fileData;
        ATTR_RECORD *mft_attr80 = this->attrs[0x80];
        //没有找到80属性
        if(!mft_attr80) {
            FileInode file=this->getInode();
            cerr <<file.name<< " no find data attr!" <<endl;
            return fileData;
        }
        if (mft_attr80->non_resident == 0) { //常驻属性    
            int offset = mft_attr80->name_offset;
            if (!offset) offset = 0x18;
            char *length = (char *) mft_attr80 + 0x10;

            string fileDataSimpe((char*) mft_attr80 + offset, *((int *) length));
            return fileDataSimpe;
        }

        char *run = (char *) mft_attr80 + 0x40;
        char *end = (char *) mft_attr80 + mft_attr80->length;
        long beforLcn = 0, count = 0;
        
        do {
            long lcn = 0, size = 0;
            int len = this->parseRunData(run, lcn, size);
            lcn += beforLcn;
            //  printf("%d,%d,%ld,%ld,%d\n",n1,n2,lcn,size,boot_sector.number_of_sectors);
            //lcn值过大时推出循环并给出警告
            if(lcn<0 || lcn> this->size || size<0 || size>this->config_maxFileSize) {
                FileInode file=this->getInode();
                cerr <<file.name<< " readfile lcn err: "<< lcn<<","<<size <<endl;
                break;
            }
            //读取大小大于0 也就是说不是全部读取出来 
            if (readLen > 0) {
                count += size * 512;
                //跳过数据大于0 也就是说已经读取了一部分内容了 
                if (readStart > 0 && count < readStart)  continue;
                
                long readNum = min(size * 512, readLen);
                fileData.append(ds->get(lcn, readNum / 512), readNum);
            } else{
                fileData.append(ds->get(lcn, size), size * 512);
            }
            
            beforLcn += lcn;
            run += len;
            if(readLen && count > readStart+readLen) break;
        } while (run[0] != 0 && run < end);
        
        return fileData;
    }
    
    
    //判断是不是一个文件  
    inline bool isFile() {
        return (strncmp((char *) mft_record, "FILE0", 5) == 0);
    }

    //转向到一个扇区
    inline bool toPostion() {
        mft_record = (MFT_RECORD *) ds->get(postion, 2);        
        if (!this->isFile()) return false;
        attrs = this->fileAttrs();
        name = (FILE_NAME_ATTR *)this->attrs[0x30];
        return !!name;
    }
    
    /**
     * 解析一个数据运行 
     * @param run 运行字符串 指针
     * @param lcn 运行的偏移lcn （单位簇） 
     * @param size 运行大小（单位簇） 
     * @return 
     */
    inline int parseRunData(char *run, long &lcn, long &size) {
        int i;
        char n1 = (*run >> 4) & 0x0f, n2 = *run & 0x0f;
        lcn = 0;
        size = 0;
        
        for (i = n1 + n2; i > n2; i--)
            lcn = (lcn << 8) +(unsigned char) run[i];
        for (i = n2; i > 0; i--)
            size = (size << 8) + (unsigned char) run[i];

        lcn *= boot_sector.sectors_per_cluster;
        size *= boot_sector.sectors_per_cluster;

        return n1 + n2 + 1;
    }
    
    /**
     * 解析文件名
     * @return 
     */
    char *parseFileName(FILE_NAME_ATTR *attrName=NULL) {
        if(attrName == NULL)  attrName=name;
        char *rt;
        int size = (int) attrName->file_name_length;
        char *a = (char *) attrName->file_name; //substr((char *)name->file_name,0,size); 
        rt= Nfs::parseUNICODE(a, size);
                
        if(attrName==name  && strchr(rt,'~') && this->attrs.count(0x31)){
            for(int i=1;i<10;i++) {
                rt = this->parseFileName((FILE_NAME_ATTR* )this->attrs[0x31]);
                if(strchr(rt,'~') == NULL) break; 
            }
        }
        
        return rt;
    }



    /**
     * 寻找下一个文件记录簇的位置 这里实现了一个块跳跃
     */
    void findNext() {
        long jumpb = this->config_jumpM * 1024 * 2, jumpa = 0, j = 0; //100M
//        printf("jump start:");
        
        while (postion < boot_sector.number_of_sectors) {
            printf("\033[%dD\033[K",20);
            cout << postion << "(" << postion * 100 / boot_sector.number_of_sectors << "%)" << ",";
            
            postion += 2;
            char *mftItem = ds->get(postion);
            
            if (strncmp(mftItem, "FILE0", 5) == 0) {
                if (jumpa > 0) {        //跳转之后的反向查找
                    long end = jumpb;
                    
                    while (end > 2) {
                        cout  << "\033[20D\033[K" << postion << "." << end << " ,";
                        end = end / 2;
                        postion+= (strncmp(mftItem, "FILE0", 5) == 0)  ?  -end:end;
                        mftItem = ds->get(postion);
                    }
                    if (!(strncmp(mftItem, "FILE0", 5) == 0)) postion += 2;
                    jumpa = 0; 
                    
                    if(! this->toPostion()) continue;   //处理名字为空的情况
                    this->checkPostion();
                }

                if(! this->toPostion()) continue;   //处理名字为空的情况
                //this->checkPostion();
                
                break;
            }

            if (j < 100) {
                j++;
            } else {
                MFT_BLOCK *b=&mft_blocks[mft_blocks.size()-1];
                if(b->size==0){
                    b->size=(fileIndex - b->index+1)*2 ;
                    if(b->size< this->config_minBlockSize)
                            mft_blocks.pop_back();  //小于512个文件不算一个块 
                }
                
                postion += jumpb;
                jumpa = postion;
            }
        }
    }
    
    /**
     * 根据文件id 进行定位 （有时候可能不太准，需要根据块信息的正确性决定 ） 
     * @param index   MFT文件偏移id
     * @return 
     */
     bool toIndex(long index) {
        MFT_BLOCK b = *(mft_blocks.begin());
        vector <MFT_BLOCK>::iterator it;
        for (it = mft_blocks.begin(); it != mft_blocks.end(); it++) {
            if (index > (*it).index) break;
            b = *it;
        }
        
        postion = b.postion + (index - b.index)*2;
        return this->toPostion();
    }
     
    /**
     * 返回一个目录下的文件名 和 文件记录id
     * @return 
     */
    map <string, long> listdir(long index=0) {
        static map <string, long> cache;
        static long cachePostion;
        if(cachePostion == postion) return cache;
        cachePostion=postion ;
        
        map <string, long> rt;
        IndexHeader *index_root = (IndexHeader *)this->attrs[0x90];
        char *attrA0 = (char *) this->attrs[0xA0];
        if (!index_root && ! attrA0) return rt;
        
        char *itemp;
        char *end ;
        
        //索引头标志 0 小索引使用与索引根 1 大索引适用与索引分配
        if (index_root && index_root->flag == 0) {
            itemp = (char *) index_root->item0;
            end= (char *) index_root + (int) index_root->index_length;
            _listdir(itemp,end,&rt);
        } else {
            char *run = attrA0 + 0x48;
            long lcn, size;
            this->parseRunData(run, lcn, size);

            char *indxp=ds->get(lcn, size);
            char *indxEnd=indxp+size*512;
//            cout << lcn << ","<< size<<endl;        
             int nextIndx=1;
            while(nextIndx && indxp<indxEnd){
                INDEX_BLOCK *ib = (INDEX_BLOCK *)indxp;
                if (strncmp((char *) ib->magic, "INDX", 4) != 0) {
                    //cerr << "\n waning:目录内容被覆盖：parentId:" << index << " postion=" << postion << " run=" << lcn << "," << size << endl;
                    break;
                }
                itemp = (char *) ib + 0x18 + ib->entries_offset;
                end = (char *) ib + ib->index_length;
                nextIndx=ceil(ceil((end-indxp)/512)/8)*this->itemSize;
                _listdir(itemp,end,&rt);
                indxp+=nextIndx*512;
            }
        }

        return cache=rt;
    }
    
    void _listdir(char *itemp,char *end,map <string, long> *rt){
        while (itemp < end) {
            IndexItem *index_item = (IndexItem *) itemp;
             
            string fname = Nfs::parseUNICODE((char *) index_item->FileName, index_item->FileNameLen);
//            cout <<fname<<endl;
//ds->dump((char *) index_item->FileName, index_item->FileNameLen*2);
            long index = Nfs::strLenVal((char *) index_item->MFTnum, 6);

            (*rt)[fname] = index;
            itemp += (short) index_item->ItemLen;
        }
    }
    
    bool _checkPostion(FileInode *file,map <string, long> *filesmap){       
        long nowPostion=postion;
        FileInode fileParent;
        
        postion = (block).postion + (file->parent - (block).index)*2;
        if (postion < this->size && this->toPostion()) {
//              fileParent = this->getInfo();
            *filesmap = this->listdir(file->parent);
        }
        postion=nowPostion;
        return !(filesmap->size() == 0 || !(*filesmap)[file->name]);
    }

    void blocks_push(long index){
                long size=0;
               MFT_BLOCK bn;
               bn.postion = postion;
               bn.index = index; 
               if(!bn.index) bn.index= fileIndex+1;
               bn.size = size; 
               mft_blocks.push_back(bn);
               block = mft_blocks.back();
//               block.size= mft_blocks.size() -1;
       }
        
    /**
     * 用于跳转之后的位置检查 （对应位置和fileIndex） 
     */
    void checkPostion() {
        this->blocks_push((long)mft_record->mft_record_number);
        return; 
        
        static int continueCheckCount;
        nextCheck=false;
        FileInode file = this->getInfo();
        printf("\33[2K\rCheckPostion:%ld",postion);
        fileIndex = (postion - block.postion)/2 + block.index;
        map <string, long> filesmap;
        MFT_BLOCK block_back=block;
         
        //只找最后三个block块  
        for(int i=mft_blocks.size(),j=3;j-- && i;i--){
                block.postion = postion;          block.index = fileIndex; 
                if(_checkPostion(&file,&filesmap)) break;
            
                block=mft_blocks.at(i-1); 
               if(_checkPostion(&file,&filesmap))           break;
        }
        block=block_back;
        this->toPostion();
        //文件无法校验 下一个文件继续检验
        if (filesmap.size() == 0 || !filesmap[file.name]) {
                nextCheck = true;
                if(continueCheckCount++ > 200) nextCheck=false;
        } else if (filesmap[file.name] != fileIndex) {
            cerr << "\r waning: fiieIndex check error: name="<<file.name<<" ," << postion << "=" << fileIndex << " fix to " << filesmap[file.name] ;
            if(mft_blocks[mft_blocks.size()-1].size==0) {
                block.postion=postion;block.index=filesmap[file.name];
                continueCheckCount=0;
                return ; 
            }
                continueCheckCount=0;
                this->blocks_push(filesmap[file.name]);
            this->dumpBlocks(&mft_blocks);        // getchar();
        }
        
    }
    
    
    void _dump(int size = 0x1FF) {
//        printf("postion=%ld,fileIndex=%ld\n",postion,fileIndex);
        if (!this->isFile())    return  ds->dump((char *) mft_record, size);
        char *p=(char *) mft_record;
        int i;
        char str[20];  
        ATTR_RECORD *arp=NULL;
        memset(str,0,sizeof(str));
        if(size==0) size=1024;
        int line=1;
        printf("\n 0  1  2  3  4  5  6  7    8  9  A  B  C  D  E  F \n");
        for (i = 0; i < size; i++) {
            if (i % 16 == 0 && i != 0)  { 
                if(p-(char *)mft_record > mft_record->bytes_in_use) break;
                printf("\t|%s|",str);
                cout << template_getNodes() << endl;
                str[0]='\0';
            }
            if (i % 16 == 8)  printf("  ");
            
            str[i%16]= isprint(p[0])?  p[0] :  '.' ;
            
            foreach(this->attrs,it){
                if(p==(char *)& *it->second){
                    arp=(ATTR_RECORD *)p;
                    break;
                }else if(arp && p > (char*)arp+arp->length){
                    arp=NULL;
                }
            }
            
            int pos=p-(char *) mft_record;
            int color=templateAttr[Template_MFT][pos];
            setColor(0,color,color?true:false);
            
            //注释
           map<int,TEMPLATENOTE> node=templateNodeName[Template_MFT];
           if(color && node.count(pos)>0) {
               templateNotes.push_back(node[pos]);
           }
            
            if(arp) template_setColorByAttr(arp,p);
            printf("%2.2x ", /* (unsigned int) p & 0xff*/(unsigned char)*p++);
            setColor();
        }
        
        printf("\t|%s|",str);
        cout << template_getNodes() << endl;
        return;
    }
    
    void dumpAttrs() {
        map <int, ATTR_RECORD *>::iterator it;
        for (it = attrs.begin(); it != attrs.end(); it++) {
            printf("%2X,%p\n", (*it).first, (*it).second);
        }
    }

    void dumpBlocks(vector <MFT_BLOCK> *b) {
        vector <MFT_BLOCK>::iterator it;
        for (it = b->begin(); it != b->end(); it++) {
            cout << it - b->begin() << ". pos：" << (*it).postion << "|" <<(*it).postion/2048<< "M index：" << (*it).index << " size： " << (*it).size << "|" << (*it).size / 2048 << "M" << endl;
        }
    }
    
      void dumpFile(FileInode file){
                    cout << "\n  parent: "<< file.parent 
                         << "\n  type:" << file.type 
                        << "\n  size:" << file.size 
                        << "\n  name:" << file.name  
                        <<"\n  postion:"<< file.postion<< "("<<file.postion/2048<<"M)"
                                << endl;
    }
      
      bool dumpfileParent(long pos){
          long p=postion;
          postion=pos;
            this->toPostion();
            FileInode file=this->getInfo();
//            this->dumpFile(file);
                 postion=6291456+file.parent*2;
            if(!this->toPostion()) {
                postion=87162176+(file.parent-62464)*2;
                this->toPostion();
            }
            FileInode pfile=this->getInfo();
//            this->dumpFile(pfile);
            map <string, long> list=this->listdir();
//            this->dumpMap(&list);           
            printf("[%s,%s,%d,%d]\n",file.name,pfile.name,(int)list.size(),(int)list.count(file.name));
            postion = p;
            
            return list.count(file.name)>0;
      }
};

